package co.baiku.boot.common.tools;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ObjectTools {
    private static Logger log = LoggerFactory.getLogger(ObjectTools.class);

    private ObjectTools() {
    }

    /**
     * 获得对象的第一个值
     * <p>
     * 支持集合,哈希,数组和字符串
     * </p>
     *
     * @param o   目标对象
     * @param <T> 预期类型
     * @return 第一个值
     */
    public static <T> T first(Object o) {
        return first(0, null);
    }

    /**
     * 获得对象的第一个值
     * <p>
     * 支持集合,哈希,数组和字符串
     * </p>
     *
     * @param o            目标对象
     * @param defaultValue 默认值
     * @param <T>          预期类型
     * @return 第一个值
     */
    public static <T> T first(Object o, T defaultValue) {
        if (!isBlank(o)) {
            if (o instanceof Collection) {
                return (T) ((Collection) o).stream().findFirst().orElse(defaultValue);
            } else if (o instanceof Map) {
                return (T) ((Map) o).values().stream().findFirst().orElse(defaultValue);
            } else if (o instanceof Object[]) {
                return ((T[]) o)[0];
            } else if (o instanceof String) {
                return (T) o.toString().substring(0, 1);
            }
        }
        return defaultValue;
    }

    /**
     * 如果传入的对象不为空,则执行自定义函数
     *
     * @param o
     * @param function
     * @param <I>
     * @param <O>
     * @return
     */
    public static <I, O> O getIfNotNull(I o, Function<I, O> function) {
        if (o != null) {
            return function.apply(o);
        }
        return null;
    }

    /**
     * 使用分隔符拼接集合
     *
     * @param list
     * @param join
     * @return String
     */
    public static String join(Collection<?> list, String join) {
        if (isBlank(list)) {
            return "";
        }
        Iterator iterator = list.iterator();
        StringBuilder stringBuilder = new StringBuilder();
        while (iterator.hasNext()) {
            stringBuilder.append(iterator.next().toString());
            if (iterator.hasNext()) {
                stringBuilder.append(join);
            }
        }
        return stringBuilder.toString();
    }

    /**
     * 判断对象是否为空
     *
     * @param o 目标对象
     * @return boolean
     */
    public static boolean isBlank(Object o) {
        if (o == null) {
            return true;
        }
        if (o instanceof Collection) {
            return ((Collection) o).isEmpty();
        } else if (o instanceof Map) {
            return ((Map) o).isEmpty();
        } else if (o instanceof Object[]) {
            return ((Object[]) o).length == 0;
        } else if (o instanceof String) {
            return StringTools.isBlank(o.toString());
        }
        return false;
    }

    /**
     * 将key或value为空的都移除
     *
     * @param map 目标哈希
     * @param <K> key类型
     * @param <V> 值类型
     * @return Map
     */
    public static <K, V> Map<K, V> removeNull(Map<K, V> map) {
        map.entrySet().removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
        return map;
    }

    /**
     * 将key或value为空的都移除
     *
     * @param map 目标哈希
     * @param <K> key类型
     * @param <V> 值类型
     * @return Map
     */
    public static <K, V> Map<K, V> removeBlank(Map<K, V> map) {
        map.entrySet().removeIf(entry -> ObjectTools.isBlank(entry.getKey()) || ObjectTools.isBlank(entry.getValue()));
        return map;
    }

    public static <T> T nvl(T t, T defaultValue) {
        return t == null ? defaultValue : t;
    }

    /**
     * 抽出对象中的某一个字段列表
     *
     * @param list     数据列表
     * @param function 抽取方法
     * @param <T>      输入类型
     * @param <R>      输出类型
     * @return List
     */
    public static <T, R> List<R> fieldList(List<T> list, Function<T, R> function) {
        if (list == null) {
            return new ArrayList<>();
        }
        return list.stream().map(function).collect(Collectors.toList());
    }

    /**
     * 按字段分组
     *
     * @param list     数据列表
     * @param function 分组方法
     * @param <ID>     分组标识
     * @param <T>      分组值
     * @return Map
     */
    public static <ID, T> Map<ID, List<T>> groupBy(List<T> list, Function<T, ID> function) {
        if (list == null) {
            return new HashMap<>(0);
        }
        return list.stream().collect(Collectors.groupingBy(function));
    }

    /**
     * 按字段分组，返回对象的指定某一个属性
     *
     * @param list                 数据列表
     * @param groupKeyFunction     分组key方法
     * @param collectFieldFunction 收集的Field方法
     * @param <ID>                 分组标识
     * @param <T>                  分组值
     * @param <U>                  收集属性标识
     * @return Map
     */
    public static <ID, T, U> Map<ID, Set<U>> groupFieldBy(List<T> list, Function<T, ID> groupKeyFunction, Function<T, U> collectFieldFunction) {
        if (list == null) {
            return new HashMap<>(0);
        }
        return list.stream().collect(Collectors.groupingBy(groupKeyFunction, Collectors.mapping(collectFieldFunction, Collectors.toSet())));
    }

    /**
     * 将对象列表转换为索引MAP
     *
     * @param list     数据列表
     * @param function 索引方法
     * @param <ID>     索引标识
     * @param <T>      被索引值
     * @return Map
     */
    public static <ID, T> Map<ID, T> index(List<T> list, Function<T, ID> function) {
        Map<ID, T> index = new HashMap<>();
        if (list == null) {
            return index;
        }
        for (T t : list) {
            ID id = function.apply(t);
            index.put(id, t);
        }
        return index;
    }

    /**
     * 判断一个对象不是空
     *
     * @param o 目标对象
     * @return boolean
     */
    public static boolean isNotBlank(Object o) {
        return !isBlank(o);
    }

    /**
     * 反序列化
     *
     * @param bytes
     * @return
     */
    public static Object deserialize(byte[] bytes) {
        Object result = null;
        if (isBlank(bytes)) {
            return null;
        }
        try {
            ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
            try {
                ObjectInputStream objectInputStream = new ObjectInputStream(byteStream);
                try {
                    result = objectInputStream.readObject();
                } catch (ClassNotFoundException ex) {
                    throw new Exception("Failed to deserialize object type", ex);
                }
            } catch (Throwable ex) {
                throw new Exception("Failed to deserialize", ex);
            }
        } catch (Exception e) {
            log.error("Failed to deserialize", e);
        }
        return result;
    }

    /**
     * 序列化
     *
     * @param object
     * @return
     */
    public static byte[] serialize(Object object) {
        byte[] result = null;
        if (object == null) {
            return new byte[0];
        }
        try {
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
            try {
                if (!(object instanceof Serializable)) {
                    throw new IllegalArgumentException(ObjectTools.class.getSimpleName() + " requires a Serializable payload " +
                            "but received an object of type [" + object.getClass().getName() + "]");
                }
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
                objectOutputStream.writeObject(object);
                objectOutputStream.flush();
                result = byteStream.toByteArray();
            } catch (Throwable ex) {
                throw new Exception("Failed to serialize", ex);
            }
        } catch (Exception ex) {
            log.error("Failed to serialize", ex);
        }
        return result;
    }

    public static <T extends Serializable> T clone(T obj) {
        T cloneObj = null;
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            log.error("Failed to clone", e);
        }
        return cloneObj;
    }
}
